查看原文
其他

2019年蚂蚁金服面经(已拿Offer)!附答案!!

JavaGuide 2021-06-30

以下文章来源于Hollis ,作者泽林

本文转载自公众号:Hollis 作者:泽林

JavaGuide修改了少部分问题的答案和排版。

由于作者面试过程中高度紧张,本文中只列出了自己还记得的部分题目。

经历了漫长一个月的等待,终于在前几天通过面试官获悉已被蚂蚁金服录取,这期间的焦虑、痛苦自不必说,知道被录取的那一刻,一整年的阴霾都一扫而空了。

笔者面的是阿里的Java研发工程师岗,面试流程是3轮技术面+1轮hr面。

1 意外的一面

一面的时候大概是3月12号,面完等了差不多半个月才突然接到二面面试官的电话。一面可能是简历面,所以问题比较简单。

ArrayList和LinkedList区别

  • 1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  • 2. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)

  • 3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。

  • 4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

  • 5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

什么情况会造成内存泄漏

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:

  1. 这些对象是可达的,即在有向图中,存在通路可以与其相连;

  2. 这些对象是无用的,即程序以后不会再使用这些对象。

如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

什么是线程死锁?如何解决?

认识线程死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。


线程死锁示意图


下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》):

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

Output

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。

学过操作系统的朋友都知道产生死锁必须具备以下四个条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

如何避免线程死锁?

我们只要破坏产生死锁的四个条件中的其中一个就可以了。

破坏互斥条件

这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。

破坏请求与保持条件

一次性申请所有的资源。

破坏不剥夺条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

破坏循环等待条件

靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

我们对线程 2 的代码修改成下面这样就不会产生死锁了。

        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 2").start();

Output

Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2
Thread[线程 1,5,main]get resource2
Thread[线程 2,5,main]get resource1
Thread[线程 2,5,main]waiting get resource2
Thread[线程 2,5,main]get resource2

Process finished with exit code 0

我们分析一下上面的代码为什么避免了死锁的发生?

线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。

红黑树是什么?怎么实现?时间复杂度?

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。除了具备该特性之外,红黑树还包括许多额外的信息。

红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。红黑树的特性:

  1. 每个节点或者是黑色,或者是红色。

  2. 根节点是黑色。

  3. 每个叶子节点是黑色。

  4. 如果一个节点是红色的,则它的子节点必须是黑色的。

  5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

关于它的特性,需要注意的是:

第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。

第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

img

具体实现代码这里不贴了,要实现起来,需要包含的基本操作是添加、删除和旋转。在对红黑树进行添加或删除后,会用到旋转方法。旋转的目的是让树保持红黑树的特性。旋转包括两种:左旋 和 右旋。

红黑树的应用比较广泛,主要是用它来存储有序的数据,它的查找、插入和删除操作的时间复杂度是O(lgn)。

TCP 三次握手和四次挥手(面试常客)

为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。

漫画图解:

图片来源:《图解HTTP》

TCP三次握手


简单示意图:

TCP三次握手


  • 客户端–发送带有 SYN 标志的数据包–一次握手–服务端

  • 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端

  • 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端

为什么要三次握手

三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

第一次握手:Client 什么都不能确认;Server 确认了对方发送正常

第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常

第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

所以三次握手就能确认双发收发功能都正常,缺一不可。

为什么要传回 SYN

接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。

SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。

传了 SYN,为啥还要传 ACK

双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。


TCP四次挥手


断开一个 TCP 连接则需要“四次挥手”:

  • 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送

  • 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号

  • 服务器-关闭与客户端的连接,发送一个FIN给客户端

  • 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1

为什么要四次挥手

任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。

举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。

上面讲的比较概括,推荐一篇讲的比较细致的文章:https://blog.csdn.net/qzcsu/article/details/72861891

突然的二面

一面的时候大概是3月12号,面完等了差不多半个月才突然接到二面面试官的电话。

介绍项目

Storm怎么保证一致性

Storm是一个分布式的流处理系统,利用anchor和ack机制保证所有tuple都被成功处理。如果tuple出错,则可以被重传,但是如何保证出错的tuple只被处理一次呢?Storm提供了一套事务性组件Transaction Topology,用来解决这个问题。

Transactional Topology目前已经不再维护,由Trident来实现事务性topology,但是原理相同。

参考:https://dwz.cn/8bXRPexB

说一下 HashMap 以及它是否线程安全

HashMap 基于哈希表的 Map 接口的实现。HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。HashMap 中 hash 数组的默认大小是16,而且一定是2的指数。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。HashMap 实现 Iterator,支持fast-fail。

哈希表是由数组+链表组成的,它是通过把key值进行hash来定位对象的,这样可以提供比线性存储更好的性能。

img

HashMap不是线程安全的。

十亿条淘宝购买记录,怎么获取出现最多的前十个

这是一道典型的有限内存的海量数据处理的题目。一般这类题目的解答无非是以下几种:

分治,hash映射,堆排序,双层桶划分,Bloom Filter,bitmap,数据库索引,mapreduce等。

具体情形都有很多不同的方案。这类题目可以到网上搜索一下,了解下套路,后面就基本都会了。

平时有没有用linux系统,怎么查看某个进程

ps aux|grep java 查看java进程
ps aux 查看所有进程
ps –ef|grep tomcat 查看所有有关tomcat的进程
ps -ef|grep --color java 高亮要查询的关键字
kill -9 19979 终止线程号位19979的进程

说一下 Innodb 和 MySIAM 的区别

MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能。

InnoDB不支持FULLTEXT类型的索引。

InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。

对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。

DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。

LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。

说一下jvm内存模型,介绍一下你了解的垃圾收集器

其实并没有 jvm 内存模型的概念。应该是 Java 内存模型或者 jvm 内存结构,这里面试者一定要听清楚问的是哪个,再回答。

可以参考:

你说你是大数据方向的,了解哪些大数据框架

作者回答了一些zookeeper、storm、HDFS、Hbase等

其他问题

  • 100个有序的整型,如何打乱顺序?

  • 如何设计一个可靠的UDP协议?

二面大概就是这些,其中storm一致性这个问题被面试官怀疑了一下,就有点紧张,其实没答错,所以还是要对知识掌握得更明确才行。

准备充足的三面

清明节的时候例外地没有回家扫墓,因为知道自己的弱项是操作系统和海量数据题这块,所以想着恶补这方面的知识,不过之后的面试意外的并没有问到这方面的内容。

介绍项目

项目介绍完之后没问太多

介绍一下HashMap

HashMap真的是面试高频题,多次面试都问到了,一定要掌握。

介绍一下并发

这里可以把整个并发的体系都说下,包括volatile、synchronized、lock、乐观悲观锁、锁膨胀、锁降级、线程池等

银行账户读写怎么做

我说了读写锁以及可能出现死锁问题

说一下关系型数据库和非关系型数据库的区别

非关系型数据库的优势:

  1. 性能:NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高

  2. 可扩展性:同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

使用场景:日志、埋点、论坛、博客等

关系型数据库的优势:

  1. 复杂查询:可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询

  2. 事务支持:使得对于安全性能很高的数据访问要求得以实现。

使用场景:所有有逻辑关系的数据存储

如何访问链表中间节点

对于这个问题,我们首先能够想到的就是先遍历一遍整个的链表,然后计算出链表的长度,进而遍历第二遍找出中间位置的数据。这种方式非常简单。

若题目要求只能遍历一次链表,那又当如何解决问题?

可以采取建立两个指针,一个指针一次遍历两个节点,另一个节点一次遍历一个节点,当快指针遍历到空节点时,慢指针指向的位置为链表的中间位置,这种解决问题的方法称为快慢指针方法。

说下进程间通信,以及各自的区别

进程间通信是指在不同进程之间传播或交换信息。方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。

访问淘宝网页的一个具体流程,从获取ip地址,到怎么返回相关内容

先通过DNS解析到服务器地址,然后反向代理、负载均衡服务器等,寻找集群中的一台机器来真正执行你的请求。还可以介绍CDN、页面缓存、Cookie以及session等。

这个过程还包括三次握手、HTTP request中包含哪些内容,状态码等,还有OSI七层分层可以介绍。

服务器接到请求后,会执行业务逻辑,执行过程中可以按照MVC来分别介绍。

服务处理过程中是否调用其他RPC服务或者异步消息,这个过程包含服务发现与注册,消息路由。

最后查询数据库,会不会经过缓存?是不是关系型数据库?是会分库分表还是做哪些操作?

对于数据库,分库分表如果数据量大的话是有必要的,一般业务根据一个分表字段进行取模进行分表,而在做数据库操作的时候,也根据同样的规则,决定数据的读写操作对应哪张表。这种也有开源的实现的,如阿里的TDDL就有这种功能。分库分表还涉及到很多技术,比如sequence如何设置 ,如何解决热点问题等。

最后再把处理结果封装成response,返回给客户端。浏览器再进行页面渲染。

焦虑的hr面

之所以说hr面焦虑,是因为面试前我还在看IG的半决赛(实在复习不下),接到电话的时候分外紧张,在一些点上答得很差。

遇到什么挫折

这种问题主要考察面试者遇见困难是否能坚持下去,并且可以看出他的解决问题的能力。

可以简单描述挫折,并说明自己如何克服,最终有哪些收获。

职业规划

表明自己决心,首先自己不准备继续求学了,必须招工作了。然后说下自己不会短期内换行业,或者换工作,自己比较喜欢,希望可以坚持几年看自己的兴趣再规划之类的。

对阿里的认识

这个比较简答,夸就行了。

有什么崇拜的人吗

我说了詹姆斯哈登,hr小姐姐居然笑了。

这个可以说一些IT大牛。

希望去哪里就业

这个问题果断回答该公司所在的城市啊。

其他问题

有什么兴趣爱好,能拿得上台表演的有吗

记忆深刻的事情

总结

提前批更多的是考察基础知识,大公司都有自己在用的框架,你进去后基本上得重新学这些框架,所以对他们来说,基础是否扎实才是考察的关键。

基础包括: 操作系统、linxu、数据库、数据结构、算法、java(基础、容器、高并发、jvm)、计算机网络等**

建议要投资知识,从寒假到现在,先后买了9个极客时间的课程、订阅了H神的知识星球、当当买了四五本相关技术书籍…

虽然购买的课很多还来不及读(惭愧)

当时我问一个Java群的师兄,学不下了怎么办,他说,换种姿势继续学,还别说,有时候失眠的时候,我都在看极客时间或知识星球催眠自己…

要对知识做好总结,虽然以前也有记录简书的习惯,但是大多数时候都是写了不发表,自己做一个记忆的作用,3月份我给自己的要求就是,对每个知识点要做到能够有自己的理解,然后写一篇质量较好的博客总结。

面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。

最后

总之,可以拿到蚂蚁金服的offer真的很意外,也很幸运,蚂蚁金服从来是我觉得很难达到的目标,但它确实发生了,也许这就是幸福来敲门吧,我可以给到自己或其他人的建议就是,一定要把握好时机。

Don't ever let somebody tell you you can't do something, not even me. You got a dream, you gotta protect it. People can't do something by themselves,they wanna tell you you can not do it. If You want something. Go get it!

推荐阅读

项目中常用的19条MySQL优化

腾讯面试:一条SQL语句执行得很慢的原因有哪些?---不看后悔系列

不就是个短信登录API嘛,有这么复杂吗?

盘点阿里巴巴 15 款开发者工具

蚂蚁金服2019实习生面经总结(已拿口头offer)

记一次蚂蚁金服的面试经历

Java学习必备书籍推荐终极版!

我觉得技术人员该有的提问方式

Java 8 新特性最佳指南

一份Java程序员必备《Java面试进阶指南》涵盖 面试必知、Java高频面试题+重要知识点总结、面试资源等内容。扫描下方二维码了解:


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存